// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "skia/ext/bitmap_platform_device_mac.h"

#import <ApplicationServices/ApplicationServices.h>
#include <stddef.h>
#include <time.h>

#include "base/mac/mac_util.h"
#include "base/memory/ref_counted.h"
#include "skia/ext/bitmap_platform_device.h"
#include "skia/ext/platform_canvas.h"
#include "skia/ext/skia_utils_mac.h"
#include "third_party/skia/include/core/SkMatrix.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkTypes.h"

namespace skia {

namespace {

    // Returns true if it is unsafe to attempt to allocate an offscreen buffer
    // given these dimensions.
    bool RasterDeviceTooBigToAllocate(int width, int height)
    {

#ifndef SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX
#define SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX (2 * 256 * 1024 * 1024)
#endif

        int bytesPerPixel = 4;
        int64_t bytes = (int64_t)width * height * bytesPerPixel;
        return bytes > SKIA_EXT_RASTER_DEVICE_ALLOCATION_MAX;
    }

    static CGContextRef CGContextForData(void* data, int width, int height)
    {
#define HAS_ARGB_SHIFTS(a, r, g, b)             \
    (SK_A32_SHIFT == (a) && SK_R32_SHIFT == (r) \
        && SK_G32_SHIFT == (g) && SK_B32_SHIFT == (b))
#if defined(SK_CPU_LENDIAN) && HAS_ARGB_SHIFTS(24, 16, 8, 0)
        // Allocate a bitmap context with 4 components per pixel (BGRA).  Apple
        // recommends these flags for improved CG performance.

        // CGBitmapContextCreate returns NULL if width/height are 0. However, our
        // callers expect to get a canvas back (which they later resize/reallocate)
        // so we pin the dimensions here.
        width = SkMax32(1, width);
        height = SkMax32(1, height);
        CGContextRef context = CGBitmapContextCreate(data, width, height, 8, width * 4,
            base::mac::GetSystemColorSpace(),
            kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
#else
#error We require that Skia's and CoreGraphics's recommended \
       image memory layout match.
#endif
#undef HAS_ARGB_SHIFTS

        if (!context)
            return NULL;

        // Change the coordinate system to match WebCore's
        CGContextTranslateCTM(context, 0, height);
        CGContextScaleCTM(context, 1.0, -1.0);

        return context;
    }

} // namespace

void BitmapPlatformDevice::ReleaseBitmapContext()
{
    SkASSERT(bitmap_context_);
    CGContextRelease(bitmap_context_);
    bitmap_context_ = NULL;
}

// Loads the specified Skia transform into the device context
static void LoadTransformToCGContext(CGContextRef context,
    const SkMatrix& matrix)
{
    // CoreGraphics can concatenate transforms, but not reset the current one.
    // So in order to get the required behavior here, we need to first make
    // the current transformation matrix identity and only then load the new one.

    // Reset matrix to identity.
    CGAffineTransform orig_cg_matrix = CGContextGetCTM(context);
    CGAffineTransform orig_cg_matrix_inv = CGAffineTransformInvert(orig_cg_matrix);
    CGContextConcatCTM(context, orig_cg_matrix_inv);

    // assert that we have indeed returned to the identity Matrix.
    SkASSERT(CGAffineTransformIsIdentity(CGContextGetCTM(context)));

    // Convert xform to CG-land.
    // Our coordinate system is flipped to match WebKit's so we need to modify
    // the xform to match that.
    SkMatrix transformed_matrix = matrix;
    SkScalar sy = -matrix.getScaleY();
    transformed_matrix.setScaleY(sy);
    size_t height = CGBitmapContextGetHeight(context);
    SkScalar ty = -matrix.getTranslateY(); // y axis is flipped.
    transformed_matrix.setTranslateY(ty + (SkScalar)height);

    CGAffineTransform cg_matrix = skia::SkMatrixToCGAffineTransform(transformed_matrix);

    // Load final transform into context.
    CGContextConcatCTM(context, cg_matrix);
}

static void LoadClippingRegionToCGContext(CGContextRef context,
    const SkIRect& clip_bounds,
    const SkMatrix& transformation)
{
    // CoreGraphics applies the current transform to clip rects, which is
    // unwanted. Inverse-transform the rect before sending it to CG. This only
    // works for translations and scaling, but not for rotations (but the
    // viewport is never rotated anyway).
    SkMatrix t;
    bool did_invert = transformation.invert(&t);
    if (!did_invert)
        t.reset();

    SkRect rect = SkRect::Make(clip_bounds);
    t.mapRect(&rect);
    SkIRect irect;
    rect.round(&irect);
    CGContextClipToRect(context, skia::SkIRectToCGRect(irect));
}

void BitmapPlatformDevice::LoadConfig(const SkMatrix& transform,
    const SkIRect& clip_bounds)
{
    if (!bitmap_context_)
        return; // Nothing to do.

    // We must restore and then save the state of the graphics context since the
    // calls to Load the clipping region to the context are strictly cummulative,
    // i.e., you can't replace a clip rect, other than with a save/restore.
    // But this implies that no other changes to the state are done elsewhere.
    // If we ever get to need to change this, then we must replace the clip rect
    // calls in LoadClippingRegionToCGContext() with an image mask instead.
    CGContextRestoreGState(bitmap_context_);
    CGContextSaveGState(bitmap_context_);
    LoadTransformToCGContext(bitmap_context_, transform);
    LoadClippingRegionToCGContext(bitmap_context_, clip_bounds, transform);
}

// We use this static factory function instead of the regular constructor so
// that we can create the pixel data before calling the constructor. This is
// required so that we can call the base class' constructor with the pixel
// data.
BitmapPlatformDevice* BitmapPlatformDevice::Create(CGContextRef context,
    int width,
    int height,
    bool is_opaque,
    bool do_clear)
{
    if (RasterDeviceTooBigToAllocate(width, height))
        return NULL;

    SkBitmap bitmap;
    // TODO: verify that the CG Context's pixels will have tight rowbytes or pass in the correct
    // rowbytes for the case when context != NULL.
    bitmap.setInfo(SkImageInfo::MakeN32(width, height, is_opaque ? kOpaque_SkAlphaType : kPremul_SkAlphaType));

    void* data;
    if (context) {
        data = CGBitmapContextGetData(context);
        bitmap.setPixels(data);
    } else {
        if (!bitmap.tryAllocPixels())
            return NULL;
        data = bitmap.getPixels();
    }
    if (do_clear)
        memset(data, 0, bitmap.getSafeSize());

        // If we were given data, then don't clobber it!
#ifndef NDEBUG
    if (!context && is_opaque) {
        // To aid in finding bugs, we set the background color to something
        // obviously wrong so it will be noticable when it is not cleared
        bitmap.eraseARGB(255, 0, 255, 128); // bright bluish green
    }
#endif

    if (!context) {
        context = CGContextForData(data, width, height);
        if (!context)
            return NULL;
    } else
        CGContextRetain(context);

    BitmapPlatformDevice* rv = new BitmapPlatformDevice(context, bitmap);

    // The device object took ownership of the graphics context with its own
    // CGContextRetain call.
    CGContextRelease(context);

    return rv;
}

BitmapPlatformDevice* BitmapPlatformDevice::CreateWithData(uint8_t* data,
    int width,
    int height,
    bool is_opaque)
{
    CGContextRef context = NULL;
    if (data)
        context = CGContextForData(data, width, height);

    BitmapPlatformDevice* rv = Create(context, width, height, is_opaque, false);

    // The device object took ownership of the graphics context with its own
    // CGContextRetain call.
    if (context)
        CGContextRelease(context);

    return rv;
}

// The device will own the bitmap, which corresponds to also owning the pixel
// data. Therefore, we do not transfer ownership to the SkBitmapDevice's bitmap.
BitmapPlatformDevice::BitmapPlatformDevice(
    CGContextRef context, const SkBitmap& bitmap)
    : SkBitmapDevice(bitmap)
    , bitmap_context_(context)
{
    SetPlatformDevice(this, this);
    SkASSERT(bitmap_context_);
    // Initialize the clip region to the entire bitmap.

    SkIRect rect;
    rect.set(0, 0,
        CGBitmapContextGetWidth(bitmap_context_),
        CGBitmapContextGetHeight(bitmap_context_));
    CGContextRetain(bitmap_context_);
    // We must save the state once so that we can use the restore/save trick
    // in LoadConfig().
    CGContextSaveGState(bitmap_context_);
}

BitmapPlatformDevice::~BitmapPlatformDevice()
{
    if (bitmap_context_)
        CGContextRelease(bitmap_context_);
}

CGContextRef BitmapPlatformDevice::GetBitmapContext(const SkMatrix& transform,
    const SkIRect& clip_bounds)
{
    LoadConfig(transform, clip_bounds);
    return bitmap_context_;
}

SkBaseDevice* BitmapPlatformDevice::onCreateDevice(const CreateInfo& cinfo,
    const SkPaint*)
{
    const SkImageInfo& info = cinfo.fInfo;
    const bool do_clear = !info.isOpaque();
    SkASSERT(info.colorType() == kN32_SkColorType);
    return Create(NULL, info.width(), info.height(), info.isOpaque(), do_clear);
}

// PlatformCanvas impl

SkCanvas* CreatePlatformCanvas(CGContextRef ctx, int width, int height,
    bool is_opaque, OnFailureType failureType)
{
    const bool do_clear = false;
    sk_sp<SkBaseDevice> dev(
        BitmapPlatformDevice::Create(ctx, width, height, is_opaque, do_clear));
    return CreateCanvas(dev, failureType);
}

SkCanvas* CreatePlatformCanvas(int width, int height, bool is_opaque,
    uint8_t* data, OnFailureType failureType)
{
    sk_sp<SkBaseDevice> dev(
        BitmapPlatformDevice::CreateWithData(data, width, height, is_opaque));
    return CreateCanvas(dev, failureType);
}

} // namespace skia
